/*
 * Copyright (C) 2014 Ningbo Yuanqun Inc., All Rights Reserved.
 */
package com.yuanqunsoft.alimigration.engine;

import static com.yuanqunsoft.alimigration.common.Helper.getRelativePath;
import static com.yuanqunsoft.alimigration.common.Helper.validExt;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.yuanqunsoft.alimigration.common.GlobalConfig;
import com.yuanqunsoft.alimigration.common.Helper;
import com.yuanqunsoft.alimigration.common.PropertiesUtil;
import com.yuanqunsoft.alimigration.engine.rule.factory.RuleParserFactory;
import com.yuanqunsoft.alimigration.engine.vo.RuleImpl;
import com.yuanqunsoft.alimigration.engine.vo.ScanResult;

/**
 * <p>
 * 所有规则实现类的父类，向上提供了统一的接口方法供一个引擎调用。
 * </p>
 * <p>
 * 一个完整的扫描逻辑是：①上层用户调用入口函数parseFile->②遍历并过滤不符合类型的文件->③每次遍历到的文件以参数传入preprocess方法中
 * ->④结束预处理->⑤遍历并过滤不符合类型的文件->⑥每次遍历到的文件以参数传入executor方法中 ->⑦将executor返回的有问题的行数加入到结果集中->⑧结束处理
 * </p>
 * <p>
 * 子类的实现者可以通过重写 {@link #preprocessImpl(InputStream)} 方法来实现步骤③、④的预处理流程， 以在正式扫描前完成一些上下文初始化工作。
 * </p>
 * <p>
 * 子类的实现者必须重写 {@link #executorImpl(InputStream)} 方法来实现步骤⑥、⑦、⑧的处理流程， 完成对整个传入的待扫描文件的一项规则扫描。
 * </p>
 * 
 * @author ShaoYu
 * @version 1.0
 */
public abstract class RuleParser {

    private final Logger logger = LogManager.getLogger(getClass());

    private RuleImpl ruleImpl;
    private ScanResult scanResult;

    /**
     * 对文件进行扫描，并返回扫描结果。
     * 
     * @param targetFile
     *            要扫描的文件的绝对路径
     * @return 扫描结果
     * @throws IllegalArgumentException
     *             if the input string is null or empty
     * @throws FileNotFoundException
     *             if target file is not exist
     */
    public final ScanResult parseFile(String root, Map<String, File> targetFiles) throws FileNotFoundException {

        Helper.checkStringNullorEmpty(root, "root");

        Helper.checkNull(targetFiles, "targetFiles");

        scanResult = new ScanResult(ruleImpl.getRuleName());

        /*
         * ----------------------预扫描，并处理每次扫描返回的结果到结果集中-------------------------
         */
        for (File file : targetFiles.values()) {
            /*
             * 根据引擎类型验证是否需要扫描该文件
             */
            if (!needScan(file)) {
                continue;
            }
            try {
                /*
                 * --------------------------------------------------------------------------------------------
                 * *********** 运行子类重写preprocess方法对待扫描文件集的一个副本进行预处理 ***********
                 * --------------------------------------------------------------------------------------------
                 */
                preprocess(new FileInputStream(file));
            } catch (FileNotFoundException e) {
                logger.error(e);
                e.printStackTrace();
            }
        }

        /*
         * --------------------------------正式扫描，并处理每次扫描返回的结果到结果集中------------------------------
         */
        for (File file : targetFiles.values()) {
            /*
             * 根据引擎类型验证是否需要扫描该文件
             */
            if (!needScan(file)) {
                continue;
            }
            logger.debug("<Parse> " + getRelativePath(root, file));
            try {
                /*
                 * ---------------------------------------------------------------------------------------
                 * ******************** 对一个文件执行一次扫描过程（该过程由子类重写）********************
                 * ---------------------------------------------------------------------------------------
                 */
                List<Integer> lines = executor(new FileInputStream(file));
                if (lines != null) {
                    for (int line : lines) {
                        scanResult.addAlert(getRelativePath(root, file), line);
                    }
                }
            } catch (FileNotFoundException e) {
                logger.error(e);
                e.printStackTrace();
            }
        }
        logger.debug("<Parse> Finished!");
        return scanResult.getAlerts().size() == 0 ? null : scanResult;
    }

    /**
     * 供{@link RuleParserFactory}初始化具体的RuleParser。<br>
     * 用户不应调用该方法（调用也无法更新该RuleParser所对应的RuleImpl）。
     * 
     * @param ruleImpl
     */
    public void initRuleParser(RuleImpl ruleImpl) {
        if (this.ruleImpl == null) {
            this.ruleImpl = ruleImpl;
        }
    }

    /**
     * <p>
     * 子类重写该方法后，可以在正式扫描前进行一些预处理（预扫描、上下文环境初始化等），传入的流是经过文件类型过滤的，保证是实现类能处理的类型。
     * </p>
     * 
     * @param files
     *            待扫描的文件集
     */
    protected void preprocessImpl(InputStream inputStream) {

    }

    /**
     * <p>
     * 实现对文本的扫描和处理逻辑，传入的流是经过文件类型过滤的，保证是实现类能处理的类型。
     * </p>
     * 
     * @param inputStream
     * @return 存在问题的行数
     */
    protected abstract List<Integer> executorImpl(InputStream inputStream);

    /**
     * <p>
     * 获取Rule实现所需要的配置文件
     * </p>
     * 
     * @param propertiesFileName
     *            配置文件名称
     * @return 配置文件
     * @throws URISyntaxException
     */
    protected final File getProperties(String fileName) {
        return new File(PropertiesUtil.getRuleImplPropertiesDir() + File.separator + fileName);
    }

    private void preprocess(InputStream inputStream) {
        if (inputStream == null) {
            throw new IllegalArgumentException("The parameter 'inputStream' should not be null.");
        }

        preprocessImpl(inputStream);
    }

    private List<Integer> executor(InputStream inputStream) {
        if (inputStream == null) {
            throw new IllegalArgumentException("The parameter 'inputStream' should not be null.");
        }

        return executorImpl(inputStream);
    }

    private boolean needScan(File file) {
        boolean needScan = false;
        switch (ruleImpl.getEngineType()) {
        case JAVAFILE:
            needScan = validExt(GlobalConfig.ENGINE_PROPERTIES_SUPPORT_JAVA_FILE_TYPE, file.getName());
            break;
        case CLASSFILE:
            needScan = validExt(GlobalConfig.ENGINE_PROPERTIES_SUPPORT_CLASS_FILE_TYPE, file.getName());
            break;
        case CONFIGFILE:
            needScan = validExt(GlobalConfig.ENGINE_PROPERTIES_SUPPORT_CONFIG_FILE_TYPE, file.getName());
            break;
        }
        return needScan;
    }

    // /**
    // * <p>
    // * 对待扫描文件集的一个深拷贝操作
    // * </p>
    // *
    // * @return 待扫描文件集的一个深拷贝集
    // * @throws IOException
    // * @throws ClassNotFoundException
    // */
    // private Set<File> cloneSet(Set<File> originalSet) throws IOException, ClassNotFoundException {
    // Set<File> copy = new HashSet<File>(originalSet.size());
    // Iterator<File> iterator = originalSet.iterator();
    // while (iterator.hasNext()) {
    // ByteArrayOutputStream bos = new ByteArrayOutputStream();
    // ObjectOutputStream oos = new ObjectOutputStream(bos);
    //
    // oos.writeObject(iterator.next());
    //
    // ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    // ObjectInputStream ois = new ObjectInputStream(bis);
    //
    // copy.add((File) ois.readObject());
    // }
    // return copy;
    // }

}